/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { resolve } from 'node:path';
import { createHash } from 'node:crypto';
import { readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
import { execSync } from 'node:child_process';
import { Argument, Command, Option } from 'commander';
import { globSync } from 'glob';
import json5 from 'json5';
import semver from 'semver';

const modules = {
  'HMRouterLibrary': {
    type: 'ohpm',
    packageName: '@hadss/hmrouter',
  },
  'HMRouterTransitions': {
    type: 'ohpm',
    packageName: '@hadss/hmrouter-transitions',
  },
  'HMRouterPlugin': {
    type: 'npm',
    packageName: '@hadss/hmrouter-plugin',
  }
};

const taskSet = {
  'clear': (moduleName) => {
    console.log(`Clearing artifacts for ${moduleName}...`);
    globSearch(`artifacts/${moduleName}*.*`, (file) => {
      unlinkSync(file);
    });
  },
  'sha256': (moduleName) => {
    console.log(`Generating SHA256 for ${moduleName}...`);
    globSearch(`artifacts/${moduleName}*.*`, (file) => {
      let content = readFileSync(file);
      let hashing = createHash('sha256').update(content).digest('hex');
      writeFileSync(resolve('artifacts', `${moduleName}.sha256.${hashing.substring(0, 8)}`), hashing,
        { encoding: 'utf-8' });
    });
  },
  'ohpm': {
    workdir: () => resolve(),
    pkg: () => 'oh-package.json5',
    updatePkg: (packageInfo) => {
      return json5.stringify(packageInfo, { space: 2, quote: '"' });
    },
    buildStage: (workdir, module, buildMode) => {
      execCommand(`hvigorw assembleHar --mode module -p module=${module}@default -p product=default  -p buildMode=${buildMode}`,
        workdir);
    },
    moveStage: (module) => {
      console.log(`Moving ${module}.har to artifacts...`);
      renameSync(resolve(`${module}/build/default/outputs/default/${module}.har`),
        resolve('artifacts', `${getArtifactName(module, 'har')}`));
    }
  },
  'npm': {
    workdir: (module) => resolve(module),
    pkg: () => 'package.json',
    updatePkg: (packageInfo) => {
      return JSON.stringify(packageInfo, null, 2);
    },
    buildStage: (workdir) =>
      execCommand(`npx tsc && npm pack`, workdir),
    moveStage: (module) => {
      const packageData = readFileSync(resolve(module, 'package.json'));
      const npmPackageInfo = JSON.parse(packageData);
      const packageFileName =
        `${npmPackageInfo.name.replaceAll('@', '').replaceAll('/', '-')}-${npmPackageInfo.version}.tgz`;
      console.log(`Moving ${packageFileName} to artifacts...`);
      renameSync(resolve(`${module}/${packageFileName}`),
        resolve('artifacts', `${getArtifactName(module, 'tgz')}`));
    }
  }
};

const program = new Command();

program
  .name('build-cli.js')
  .description('Build CLI for HMRouter Project')
  .version('1.0.0');

program.command('build')
  .description('Build specific module')
  .addArgument(new Argument('[moduleName]', 'the name of module you need to build, all will be built if not specified')
    .argOptional()
    .default('all')
    .choices(['all', 'HMRouterLibrary', 'HMRouterTransitions', 'HMRouterPlugin']))
  .addOption(new Option('-m, --mode [buildMode]', 'build mode for SDK')
    .default('debug')
    .choices(['debug', 'release']))
  .action((moduleName, options) => {
    if (moduleName === 'all') {
      Object.keys(modules).forEach((item) => {
        buildModule(item, options);
      });
    } else if (modules.hasOwnProperty(moduleName)) {
      buildModule(moduleName, options);
    } else {
      console.error(`Module ${moduleName} does not exist`);
    }
  });

program.command('version')
  .description('set project version')
  .addArgument(new Argument('<version>', 'version type: major, minor, patch or prerelease')
    .argRequired()
    .choices(['premajor', 'preminor', 'prepatch', 'prerelease', 'major', 'minor', 'patch']))
  .addArgument(new Argument('[preid]', 'pre-release identifier')
    .argOptional()
    .default('rc')
    .choices(['rc', 'snapshot', 'beta']))
  .option('-d, --dryrun', 'dry run, do not change version')
  .action((version, preid, options) => {
    console.log(`Setting project version to ${version} - ${preid}...`);
    let newVersion = semver.inc(process.env.npm_package_version, version, preid);
    console.log(`Version will be changed to ${newVersion}`);
    Object.keys(modules).forEach((item) => {
      const packageFilePath = resolve(item, taskSet[modules[item].type].pkg());
      const packageData = updatePackage(packageFilePath, newVersion, modules[item].type);
      if (options.dryrun) {
        console.log('Dry run, version not changed');
        return;
      }
      writeFileSync(packageFilePath, packageData, 'utf-8');
    });
    updateRootPackage(newVersion, options);
  });


program.parse();

function buildModule(module, options) {
  console.log(`Building module ${module}...`);
  const projectDir = resolve();
  const moduleType = modules[module]['type'];
  console.log(`Project dir: ${projectDir}`);
  // clear artifact
  taskSet['clear'](module);
  const task = taskSet[moduleType];
  const workdir = task.workdir(module);
  console.log(`Workdir: ${workdir}`);
  // build artifact
  task.buildStage(workdir, module, options.mode);
  // move artifact
  task.moveStage(module);
  // hash artifact
  taskSet['sha256'](module);
}

function getArtifactName(module, suffix) {
  return `${module}-${process.env.npm_package_version}+build.${getGitCommit()}.${suffix}`;
};

function getGitCommit() {
  return execSync('git rev-parse --short HEAD', { cwd: resolve(), encoding: 'utf-8' }).trim();
};

function execCommand(command, workdir) {
  console.log(`Executing command: ${command}`);
  return execSync(command,
    { cwd: workdir, stdio: 'inherit' });
}

function globSearch(pattern, callback) {
  let matches = globSync(pattern);
  matches.forEach((file) => {
    callback(file);
  });
}

function updateRootPackage(newVersion, options) {
  globSearch('package.json', (file) => {
    const packageData = updatePackage(file, newVersion, 'npm');
    if (options.dryrun) {
      console.log('Dry run, version not changed');
      return;
    }
    writeFileSync(file, packageData, 'utf-8');
  });
}

function updatePackage(packageFilePath, newVersion, type) {
  const packageData = readFileSync(packageFilePath, 'utf-8');
  const packageInfo = json5.parse(packageData);
  packageInfo.version = newVersion;
  updateDeps(packageInfo, newVersion);
  const newPackageData = taskSet[type].updatePkg(packageInfo);
  console.log(newPackageData);
  return newPackageData;
}

function updateDeps(packageInfo, newVersion) {
  const deps = packageInfo.dependencies;
  if (!deps) {
    return;
  }
  Object.keys(deps).forEach((item) => {
    let packageName = Object.keys(modules)
      .find((element) => modules[element].packageName === item);
    if (packageName) {
      console.log(`Updating dependency ${item} to ${newVersion}`);
      packageInfo.dependencies[item] = newVersion;
    }
  });
}
